Micron Document
🎖️GitЯра🎖️


Displaying Raw • Download

feature/node/src/commonTest/kotlin/org/meshtastic/feature/node/metrics/MetricsViewModelTest.kt 520fa717a938fd84682403b41f57d9f7e1ef49ef (520fa717) Text, 8.91 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.feature.node.metrics

Tff7b72import T7ee787app.cash.turbine.test
Tff7b72import T7ee787dev.mokkery.answering.calls
Tff7b72import T7ee787dev.mokkery.answering.returns
Tff7b72import T7ee787dev.mokkery.every
Tff7b72import T7ee787dev.mokkery.everySuspend
Tff7b72import T7ee787dev.mokkery.matcher.any
Tff7b72import T7ee787dev.mokkery.mock
Tff7b72import T7ee787dev.mokkery.verifySuspend
Tff7b72import T7ee787kotlinx.coroutines.Dispatchers
Tff7b72import T7ee787kotlinx.coroutines.flow.MutableStateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.asStateFlow
Tff7b72import T7ee787kotlinx.coroutines.flow.flowOf
Tff7b72import T7ee787kotlinx.coroutines.test.StandardTestDispatcher
Tff7b72import T7ee787kotlinx.coroutines.test.resetMain
Tff7b72import T7ee787kotlinx.coroutines.test.runCurrent
Tff7b72import T7ee787kotlinx.coroutines.test.runTest
Tff7b72import T7ee787kotlinx.coroutines.test.setMain
Tff7b72import T7ee787okio.Buffer
Tff7b72import T7ee787okio.BufferedSink
Tff7b72import T7ee787org.meshtastic.core.common.util.MeshtasticUri
Tff7b72import T7ee787org.meshtastic.core.di.CoroutineDispatchers
Tff7b72import T7ee787org.meshtastic.core.repository.FileService
Tff7b72import T7ee787org.meshtastic.core.repository.MeshLogRepository
Tff7b72import T7ee787org.meshtastic.core.repository.NodeRepository
Tff7b72import T7ee787org.meshtastic.core.repository.ServiceRepository
Tff7b72import T7ee787org.meshtastic.core.repository.TracerouteSnapshotRepository
Tff7b72import T7ee787org.meshtastic.feature.node.detail.NodeDetailUiState
Tff7b72import T7ee787org.meshtastic.feature.node.detail.NodeRequestActions
Tff7b72import T7ee787org.meshtastic.feature.node.domain.usecase.GetNodeDetailsUseCase
Tff7b72import T7ee787org.meshtastic.feature.node.model.MetricsState
Tff7b72import T7ee787org.meshtastic.feature.node.model.TimeFrame
Tff7b72import T7ee787org.meshtastic.proto.Position
Tff7b72import T7ee787org.meshtastic.proto.Telemetry
Tff7b72import T7ee787kotlin.test.AfterTest
Tff7b72import T7ee787kotlin.test.BeforeTest
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.test.assertEquals
Tff7b72import T7ee787kotlin.test.assertNotNull
Tff7b72import T7ee787kotlin.test.assertTrue

Tff7b72class T56d364MetricsViewModelTest Tb4b4b4{

Tff7b72private Tff7b72val Te6edf3testDispatcher Tff7b72= Te6edf3StandardTestDispatcherTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3dispatchers Tff7b72= Te6edf3CoroutineDispatchersTb4b4b4(Te6edf3main Tff7b72= Te6edf3testDispatcherTb4b4b4, Te6edf3io Tff7b72= Te6edf3testDispatcherTb4b4b4, Te6edf3default Tff7b72= Te6edf3testDispatcherTb4b4b4)

Tff7b72private Tff7b72val Te6edf3meshLogRepositoryTb4b4b4: Te6edf3MeshLogRepository Tff7b72= Te6edf3mockTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3serviceRepositoryTb4b4b4: Te6edf3ServiceRepository Tff7b72= Te6edf3mockTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3nodeRepositoryTb4b4b4: Te6edf3NodeRepository Tff7b72= Te6edf3mockTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3tracerouteSnapshotRepositoryTb4b4b4: Te6edf3TracerouteSnapshotRepository Tff7b72= Te6edf3mockTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3nodeRequestActionsTb4b4b4: Te6edf3NodeRequestActions Tff7b72= Te6edf3mockTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3alertManagerTb4b4b4: Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3coreTb4b4b4.Te6edf3uiTb4b4b4.Te6edf3utilTb4b4b4.Te6edf3AlertManager Tff7b72= Te6edf3mockTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3getNodeDetailsUseCaseTb4b4b4: Te6edf3GetNodeDetailsUseCase Tff7b72= Te6edf3mockTb4b4b4(Tb4b4b4)
Tff7b72private Tff7b72val Te6edf3fileServiceTb4b4b4: Te6edf3FileService Tff7b72= Te6edf3mockTb4b4b4(Tb4b4b4)

Tff7b72private Tff7b72lateinit Tff7b72var Te6edf3viewModelTb4b4b4: Te6edf3MetricsViewModel

Tf0883e@BeforeTest
Tff7b72fun Td2a8ffsetUpTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3DispatchersTb4b4b4.Te6edf3setMainTb4b4b4(Te6edf3testDispatcherTb4b4b4)

T8b949e// Default setup for flows
Te6edf3every Tb4b4b4{ Te6edf3serviceRepositoryTb4b4b4.Te6edf3tracerouteResponse Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Tff7b72nullTb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRequestActionsTb4b4b4.Te6edf3lastTracerouteTime Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Tff7b72nullTb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRequestActionsTb4b4b4.Te6edf3lastRequestNeighborTimes Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Te6edf3emptyMapTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3nodeRepositoryTb4b4b4.Te6edf3nodeDBbyNum Tb4b4b4} Te6edf3returns Te6edf3MutableStateFlowTb4b4b4(Te6edf3emptyMapTb4b4b4(Tb4b4b4)Tb4b4b4)

T8b949e// Mock the case where we get node details
Te6edf3every Tb4b4b4{ Te6edf3getNodeDetailsUseCaseTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3flowOfTb4b4b4(Te6edf3NodeDetailUiStateTb4b4b4(Tb4b4b4)Tb4b4b4)

Te6edf3viewModel Tff7b72= Te6edf3createViewModelTb4b4b4(Tb4b4b4)
Tb4b4b4}

Tff7b72private Tff7b72fun Td2a8ffcreateViewModelTb4b4b4(Te6edf3destNumTb4b4b4: Tffa657Int Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4Tb4b4b4) Tff7b72= Te6edf3MetricsViewModelTb4b4b4(
Te6edf3destNum Tff7b72= Te6edf3destNumTb4b4b4,
Te6edf3dispatchers Tff7b72= Te6edf3dispatchersTb4b4b4,
Te6edf3meshLogRepository Tff7b72= Te6edf3meshLogRepositoryTb4b4b4,
Te6edf3serviceRepository Tff7b72= Te6edf3serviceRepositoryTb4b4b4,
Te6edf3nodeRepository Tff7b72= Te6edf3nodeRepositoryTb4b4b4,
Te6edf3tracerouteSnapshotRepository Tff7b72= Te6edf3tracerouteSnapshotRepositoryTb4b4b4,
Te6edf3nodeRequestActions Tff7b72= Te6edf3nodeRequestActionsTb4b4b4,
Te6edf3alertManager Tff7b72= Te6edf3alertManagerTb4b4b4,
Te6edf3getNodeDetailsUseCase Tff7b72= Te6edf3getNodeDetailsUseCaseTb4b4b4,
Te6edf3fileService Tff7b72= Te6edf3fileServiceTb4b4b4,
Tb4b4b4)

Tf0883e@AfterTest
Tff7b72fun Td2a8fftearDownTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3DispatchersTb4b4b4.Te6edf3resetMainTb4b4b4(Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8fftestInitializationTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3assertNotNullTb4b4b4(Te6edf3viewModelTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`state reflects updates from getNodeDetailsUseCase`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3nodeDetailFlow Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Te6edf3NodeDetailUiStateTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3getNodeDetailsUseCaseTb4b4b4(T79c0ff1T79c0ff2T79c0ff3T79c0ff4Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3nodeDetailFlowTb4b4b4.Te6edf3asStateFlowTb4b4b4(Tb4b4b4)

Tff7b72val Te6edf3vm Tff7b72= Te6edf3createViewModelTb4b4b4(Tb4b4b4)
Te6edf3vmTb4b4b4.Te6edf3stateTb4b4b4.Te6edf3test Tb4b4b4{
Te6edf3assertEqualsTb4b4b4(Te6edf3MetricsStateTb4b4b4.Te6edf3EmptyTb4b4b4, Te6edf3awaitItemTb4b4b4(Tb4b4b4)Tb4b4b4)

Tff7b72val Te6edf3newState Tff7b72= Te6edf3MetricsStateTb4b4b4(Te6edf3isFahrenheit Tff7b72= Tff7b72trueTb4b4b4)
Te6edf3nodeDetailFlowTb4b4b4.Te6edf3value Tff7b72= Te6edf3NodeDetailUiStateTb4b4b4(Te6edf3metricsState Tff7b72= Te6edf3newStateTb4b4b4)

Te6edf3assertEqualsTb4b4b4(Te6edf3newStateTb4b4b4, Te6edf3awaitItemTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3cancelAndIgnoreRemainingEventsTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`availableTimeFrames filters based on oldest data`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3now Tff7b72= Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3coreTb4b4b4.Te6edf3commonTb4b4b4.Te6edf3utilTb4b4b4.Te6edf3nowSeconds

Tff7b72val Te6edf3nodeDetailFlow Tff7b72= Te6edf3MutableStateFlowTb4b4b4(Te6edf3NodeDetailUiStateTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3getNodeDetailsUseCaseTb4b4b4(T79c0ff1T79c0ff2T79c0ff3T79c0ff4Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3nodeDetailFlowTb4b4b4.Te6edf3asStateFlowTb4b4b4(Tb4b4b4)

Tff7b72val Te6edf3vm Tff7b72= Te6edf3createViewModelTb4b4b4(Tb4b4b4)
Te6edf3vmTb4b4b4.Te6edf3availableTimeFramesTb4b4b4.Te6edf3test Tb4b4b4{
T8b949e// Skip initial values
Tff7b72var Te6edf3current Tff7b72= Te6edf3awaitItemTb4b4b4(Tb4b4b4)

T8b949e// Provide data from 2 hours ago (7200 seconds)
T8b949e// This should make ONE_HOUR available, but not TWENTY_FOUR_HOURS
Tff7b72val Te6edf3twoHoursAgo Tff7b72= Te6edf3now Tff7b72- T79c0ff7T79c0ff2T79c0ff0T79c0ff0
Te6edf3nodeDetailFlowTb4b4b4.Te6edf3value Tff7b72=
Te6edf3NodeDetailUiStateTb4b4b4(
Te6edf3node Tff7b72=
Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3coreTb4b4b4.Te6edf3modelTb4b4b4.Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4Tb4b4b4, Te6edf3user Tff7b72= Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3protoTb4b4b4.Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!1234Ta5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4,
Te6edf3environmentState Tff7b72=
Te6edf3EnvironmentMetricsStateTb4b4b4(Te6edf3environmentMetrics Tff7b72= Te6edf3listOfTb4b4b4(Te6edf3TelemetryTb4b4b4(Te6edf3time Tff7b72= Te6edf3twoHoursAgoTb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)

T8b949e// We might get multiple emissions as flows propagate
Te6edf3current Tff7b72= Te6edf3awaitItemTb4b4b4(Tb4b4b4)
Tff7b72while Tb4b4b4(Te6edf3currentTb4b4b4.Te6edf3size Tff7b72=Tff7b72= Te6edf3TimeFrameTb4b4b4.Te6edf3entriesTb4b4b4.Te6edf3sizeTb4b4b4) Tb4b4b4{ T8b949e// Skip the initial "all" if it's still there
Te6edf3current Tff7b72= Te6edf3awaitItemTb4b4b4(Tb4b4b4)
Tb4b4b4}

Te6edf3assertTrueTb4b4b4(Te6edf3currentTb4b4b4.Te6edf3containsTb4b4b4(Te6edf3TimeFrameTb4b4b4.Te6edf3ONE_HOURTb4b4b4)Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Tff7b72!Te6edf3currentTb4b4b4.Te6edf3containsTb4b4b4(Te6edf3TimeFrameTb4b4b4.Te6edf3TWENTY_FOUR_HOURSTb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffShould not contain 24h for 2h old dataTa5d6ff"Tb4b4b4)

T8b949e// Provide data from 8 days ago
Tff7b72val Te6edf3eightDaysAgo Tff7b72= Te6edf3now Tff7b72- Tb4b4b4(T79c0ff8 Tff7b72* T79c0ff2T79c0ff4 Tff7b72* T79c0ff3T79c0ff6T79c0ff0T79c0ff0Tb4b4b4)
Te6edf3nodeDetailFlowTb4b4b4.Te6edf3value Tff7b72=
Te6edf3NodeDetailUiStateTb4b4b4(
Te6edf3node Tff7b72=
Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3coreTb4b4b4.Te6edf3modelTb4b4b4.Te6edf3NodeTb4b4b4(Te6edf3num Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4Tb4b4b4, Te6edf3user Tff7b72= Te6edf3orgTb4b4b4.Te6edf3meshtasticTb4b4b4.Te6edf3protoTb4b4b4.Te6edf3UserTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ff!1234Ta5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4,
Te6edf3environmentState Tff7b72=
Te6edf3EnvironmentMetricsStateTb4b4b4(
Te6edf3environmentMetrics Tff7b72= Te6edf3listOfTb4b4b4(Te6edf3TelemetryTb4b4b4(Te6edf3time Tff7b72= Te6edf3eightDaysAgoTb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)

Te6edf3current Tff7b72= Te6edf3awaitItemTb4b4b4(Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3currentTb4b4b4.Te6edf3containsTb4b4b4(Te6edf3TimeFrameTb4b4b4.Te6edf3SEVEN_DAYSTb4b4b4)Tb4b4b4)

Te6edf3cancelAndIgnoreRemainingEventsTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`savePositionCSV writes correct data`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTestTb4b4b4(Te6edf3testDispatcherTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3testPosition Tff7b72=
Te6edf3PositionTb4b4b4(
Te6edf3latitude_i Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4T79c0ff5T79c0ff6T79c0ff7T79c0ff8T79c0ff9Tb4b4b4,
Te6edf3longitude_i Tff7b72= Tff7b72-T79c0ff9T79c0ff8T79c0ff7T79c0ff6T79c0ff5T79c0ff4T79c0ff3T79c0ff2T79c0ff1Tb4b4b4,
Te6edf3altitude Tff7b72= T79c0ff1T79c0ff0T79c0ff0Tb4b4b4,
Te6edf3sats_in_view Tff7b72= T79c0ff5Tb4b4b4,
Te6edf3ground_speed Tff7b72= T79c0ff1T79c0ff0Tb4b4b4,
Te6edf3ground_track Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4T79c0ff5T79c0ff6Tb4b4b4,
Te6edf3time Tff7b72= T79c0ff1T79c0ff7T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Tb4b4b4)

Tff7b72val Te6edf3nodeDetailFlow Tff7b72=
Te6edf3MutableStateFlowTb4b4b4(Te6edf3NodeDetailUiStateTb4b4b4(Te6edf3metricsState Tff7b72= Te6edf3MetricsStateTb4b4b4(Te6edf3positionLogs Tff7b72= Te6edf3listOfTb4b4b4(Te6edf3testPositionTb4b4b4)Tb4b4b4)Tb4b4b4)Tb4b4b4)
Te6edf3every Tb4b4b4{ Te6edf3getNodeDetailsUseCaseTb4b4b4(T79c0ff1T79c0ff2T79c0ff3T79c0ff4Tb4b4b4) Tb4b4b4} Te6edf3returns Te6edf3nodeDetailFlowTb4b4b4.Te6edf3asStateFlowTb4b4b4(Tb4b4b4)

Tff7b72val Te6edf3buffer Tff7b72= Te6edf3BufferTb4b4b4(Tb4b4b4)
Te6edf3everySuspend Tb4b4b4{ Te6edf3fileServiceTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4} Te6edf3calls
Tb4b4b4{ Te6edf3args Tff7b72-Tff7b72>
Tff7b72val Te6edf3block Tff7b72= Te6edf3argsTb4b4b4.Te6edf3argTff7b72<Te6edf3suspend Tb4b4b4(Te6edf3BufferedSinkTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTff7b72>Tb4b4b4(T79c0ff1Tb4b4b4)
Te6edf3blockTb4b4b4(Te6edf3bufferTb4b4b4)
Tff7b72true
Tb4b4b4}

Tff7b72val Te6edf3vm Tff7b72= Te6edf3createViewModelTb4b4b4(Tb4b4b4)
T8b949e// Wait for state to be collected so it's not Empty when savePositionCSV is called
Te6edf3vmTb4b4b4.Te6edf3stateTb4b4b4.Te6edf3test Tb4b4b4{
Te6edf3awaitItemTb4b4b4(Tb4b4b4) T8b949e// Empty
Te6edf3awaitItemTb4b4b4(Tb4b4b4) T8b949e// with position

Tff7b72val Te6edf3uri Tff7b72= Te6edf3MeshtasticUriTb4b4b4(Ta5d6ff"Ta5d6ffcontent://testTa5d6ff"Tb4b4b4)
Te6edf3vmTb4b4b4.Te6edf3savePositionCSVTb4b4b4(Te6edf3uriTb4b4b4)
Te6edf3runCurrentTb4b4b4(Tb4b4b4)

Te6edf3verifySuspend Tb4b4b4{ Te6edf3fileServiceTb4b4b4.Te6edf3writeTb4b4b4(Te6edf3uriTb4b4b4, Te6edf3anyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4}

Tff7b72val Te6edf3csvOutput Tff7b72= Te6edf3bufferTb4b4b4.Te6edf3readUtf8Tb4b4b4(Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3csvOutputTb4b4b4.Te6edf3startsWithTb4b4b4(Ta5d6ff"Ta5d6ff\"Ta5d6ffdateTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fftimeTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fflatitudeTa5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6fflongitudeTa5d6ff\"Ta5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3csvOutputTb4b4b4.Te6edf3containsTb4b4b4(Ta5d6ff"Ta5d6ff12.3456789Ta5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3csvOutputTb4b4b4.Te6edf3containsTb4b4b4(Ta5d6ff"Ta5d6ff-98.7654321Ta5d6ff"Tb4b4b4)Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3csvOutputTb4b4b4.Te6edf3containsTb4b4b4(Ta5d6ff"Ta5d6ff\"Ta5d6ff100Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ff5Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ff10Ta5d6ff\"Ta5d6ff,Ta5d6ff\"Ta5d6ff1.23Ta5d6ff\"Ta5d6ff"Tb4b4b4)Tb4b4b4)

Te6edf3cancelAndIgnoreRemainingEventsTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.08s